/*
 * file Node.cpp 
 * 
 * brief  Node 
 * author Bas du Pre
 * date   19-05-2011
 *
 * details Bla
 * 
 **/ 

#include "graphics/Node.h"

#include <math.h>
#include <iostream>

#ifndef PI
#define PI 3.141592
#endif

using namespace Prism;
using namespace Prism::Graphics;

Node::Node() :
_z(0),
_shouldSortChildren(false),
_scale(PointF(1.0 ,1.0)),
_rotation(0),
_visible(true),
_anchorPoint(PointF(0.5, 0.5)),
_parent(0),
_isRelativeAnchorPoint(true),
_tag(0),
_userData(0)
{
    native_init();
    //Changed.addHandler(this, &Node::onChanged);
    
}

Node::~Node() {
    
}

void Node::addChild(Node *node) {
    addChild(node, 0);
}

void Node::addChild(Node *node, PInt z) {
    //std::cout << "Add child at z: " << z << std::endl;
    _childs.pushBack(node);
    node->_parent = this;
    node->setZOrder(z);
    EventArgs e;
    node->onParentChange(node, &e);
}

void Node::addChild(Node *node, PInt z, PInt tag) {
    _childs.pushBack(node);
    node->_parent = this;
    node->setTag(tag);
    node->setZOrder(z);
    EventArgs e;
    node->onParentChange(node, &e);
}

void Node::removeChild(Node *node) {
    if (_childs.remove(node)) {
        node->setParent(0);
    }
}

void Node::removeChildByTag(PInt tag) {
    for (unsigned int i = 0; i < _childs.length(); ++i) {
        if (_childs[i]->tag() == tag)
            removeChild(_childs[i]);
    }
}

void Node::removeAllChildren() {
    for (unsigned int i = 0; i < _childs.length(); ++i) {
        removeChild(_childs[i]);
    }
}

List<Node*> Node::getChildrenByTag(PInt tag) {
    if (_shouldSortChildren) sortChildren();
    List<Node*> nodes;
    for (unsigned int i = 0; i < _childs.length(); ++i) {
        if (_childs[i]->tag() == tag)
            nodes.pushBack(_childs[i]);
    }
    return nodes;
}

void Node::reorderChild(Node *child, PInt z) {
    child->_z = z;
    _shouldSortChildren = true;
    EventArgs e;
    onZOrderChange(child, &e);
}

bool Node::compareByZOrder(Node *a, Node *b) {
    return a->zOrder() < b->zOrder();
}


bool Node::compareByScreenZOrder(Node *a, Node *b) {
    return a->screenZ() > b->screenZ();
}

void Node::sortChildren() {
    _childs.sort(&Node::compareByZOrder);
    _shouldSortChildren = false;
}

Prism::Rect Node::boundingBox() {
    Prism::Rect r;
    r.size.width = 0;
    r.size.height = 0;
    r.origin.x = _position.x;
    r.origin.y = _position.y;
    return r;
}

void Node::draw() {
    
}

void Node::setZOrder(PInt z) {
    if (parent() != 0)
        parent()->reorderChild(this, z);
    else
        _z = z;
}

PInt Node::zOrder() {
    return _z;
}

void Node::setRotation(PFloat rotation) {
    _rotation = rotation;
    EventArgs e;
    onRotationChange(this, &e);
}

PFloat Node::rotation() {
    return _rotation;
}

void Node::setScale(PFloat scale) {
    _scale.x = scale;
    _scale.y = scale;
    EventArgs e;
    onScaleChange(this, &e);
}

PFloat Node::scale() {
    PRISM_ASSERT(_scale.x == _scale.y, "Scale : I don't know which one to return...");
    return _scale.x;
}

void Node::setScaleX(PFloat scaleX) {
    _scale.x = scaleX;
    EventArgs e;
    onScaleChange(this, &e);
}

PFloat Node::scaleX() {
    return _scale.x;
}

void Node::setScaleY(PFloat scaleY) {
    _scale.y = scaleY;
    EventArgs e;
    onScaleChange(this, &e);
}

PFloat Node::scaleY() {
    return _scale.y;
}

void Node::setPosition(PointF position) {
    _position = position;
    EventArgs e;
    onPositionChange(this, &e);
}

void Node::setPosition(PFloat x, PFloat y) {
    _position.x = x;
    _position.y = y;
    EventArgs e;
    onPositionChange(this, &e);
}

Prism::PointF Node::position() {
    return _position;
}

PFloat Node::screenScaleX() {
    if (hasParent()) {                
        return parent()->screenScaleX() * scaleX();
    } else {
        return this->scaleX();
    }
}

PFloat Node::screenScaleY() {
    if (hasParent()) {                
        return parent()->screenScaleY() * scaleY();
    } else {
        return this->scaleY();
    }
}

PFloat Node::screenRotation() {
    if (hasParent()) {                
        return parent()->screenRotation() + rotation();
    } else {
        return this->rotation();
    }
}

Prism::PointF Node::screenPosition() {
    if (hasParent()) {
        Prism::PointF position = parent()->screenPosition();
                
        if (parent()->screenRotation() != 0) {
            //PFloat a = abs(a = position.x - this->position().x);
            //PFloat b = abs(b = position.y - this->position().y);
            //PFloat radius = sqrtf(a*a + b*b);
            
            PFloat radians = parent()->screenRotation() * PI / 180;
            position.x += (cos(radians) * this->position().x
                           - sin(radians) * this->position().y) * parent()->screenScaleX();
            position.y += (cos(radians) * this->position().y
                           + sin(radians) * this->position().x) * parent()->screenScaleY();
        } else {
            position.x += this->position().x * parent()->screenScaleX();
            position.y += this->position().y * parent()->screenScaleY();
        }
        
        return position;
    } else {
        return this->position();
    }
}

void Node::setVisible(bool visible) {
    _visible = visible;
    EventArgs e;
    onVisibleChange(this, &e);
}

bool Node::visible() {
    return _visible;
}

void Node::setAnchorPoint(PointF anchorPoint) {
    _anchorPoint = anchorPoint;
    EventArgs e;
    onAnchorPointChange(this, &e);
}

void Node::setAnchorPoint(PFloat x, PFloat y) {
    _anchorPoint.x = x;
    _anchorPoint.y = y;
    EventArgs e;
    onAnchorPointChange(this, &e);
}

Prism::PointF Node::anchorPoint() {
    return _anchorPoint;
}

Node *Node::parent() {
    return _parent;
}

bool Node::hasParent() {
    return _parent != 0;
}

void Node::setParent(Node *parent)
{
    if (_parent != 0) _parent->removeChild(this);
    if (parent != 0) parent->addChild(this);
    EventArgs e;
    onParentChange(this, &e);
}

void Node::setIsRelativeAnchorPoint(bool isRelativeAnchorPoint) {
    _isRelativeAnchorPoint = isRelativeAnchorPoint;
    EventArgs e;
    onIsRelativeAnchorPointChange(this, &e);
}

bool Node::isRelativeAnchorPoint() {
    return _isRelativeAnchorPoint;
}

void Node::setTag(PInt tag) {
    _tag = tag;
    EventArgs e;
    onTagChange(this, &e);
}

PInt Node::tag() {
    return _tag;
}

void Node::setUserData(void *userData) {
    _userData = userData;
    EventArgs e;
    onUserDataChange(this, &e);
}

void* Node::userData() {
    return _userData;
}

PInt Node::_staticScreenZ = 0;

PInt Node::getNextZ() {
    return Node::_staticScreenZ++;
}

void Node::visit() {
    // quick return if not visible
	if (!visible())
		return;
    
    //std::cout << "visit node z: " << _z;
    
	if(_childs.length() > 0) {
        if (_shouldSortChildren) sortChildren();
		
        //std::cout << " (with " << _childs.length() << " childs)";
        
        unsigned int i = 0;
        
		// draw children zOrder < 0
		for( ; i < _childs.length(); i++) {
			if (_childs[i]->zOrder() < 0) {
				_childs[i]->visit();
            }
			else
				break;
		}
		
		// self draw
        _screenZ = Node::getNextZ();
		draw();
        native_visit();
		
		// draw children zOrder >= 0
		for( ; i < _childs.length(); i++) {
            _childs[i]->visit();
		}
        
	} else {
        _screenZ = Node::getNextZ();
		draw();
        native_visit();
    }
    
    //std::cout << std::endl;
}

PInt Node::screenZ() {
    return _screenZ;
}

List<Node*> Node::hitTest(PointF point, bool shouldSort) {
    // Point is a screen coordinate
    List<Node*> hits;
    
    if (!visible()) return hits;
    
    if (coversPoint(point)) hits.pushBack(this);
    
    for (unsigned int i = 0; i < _childs.length(); i++) {
        List<Node*> hits2 = _childs[i]->hitTest(point, false);
        hits.append(hits2);   
    }
    
    if (shouldSort) hits.sort(&Node::compareByScreenZOrder);
    
    return hits;
}

bool Node::coversPoint(PointF point) {
    Rect bb = boundingBox();
    if (point.x >= bb.origin.x && point.x <= bb.origin.x + bb.size.width) {
        if (point.y >= bb.origin.y && point.y <= bb.origin.y + bb.size.height) {
            return true;
        }
    }
    return false;
}

String Node::toString() {
    return String::fromFormat("[Node x='%d' y='%d']", _position.x, _position.y);
}
